﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ionic.Zlib;
using LoLScrape.Inibin;

namespace LoLScrape
{
    public class InibinReader
    {   
        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static Dictionary<UInt32, Object> Scrape(CrcCalculatorStream stream)
        {
            Dictionary<UInt32, Object> dictProperties = new Dictionary<UInt32, Object>();

            //Read the inibin's header
            InibinHeader header = ReadHeader(stream);

            //Read each Segment based on the segment flags
            ReadSegments(stream, header, dictProperties);

            //Store the Segment Flag information in Key Value 0
            dictProperties.Add(0, header.intSegmentFlags);
            
            return dictProperties;
        } //end scrapeInibin

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        private static InibinHeader ReadHeader(CrcCalculatorStream stream)
        {
            InibinHeader header = new InibinHeader();

            //Retrieve file length
            header.intStringLength = (Int64)stream.Length;

            //Retrieve Version
            header.intVersion = (Int16)ByteReader.ReadInt8(stream);

            if (header.intVersion != 2)
            {
                throw new NotImplementedException();
            } //end if

            //Retrieve Offset
            header.intStringOffset = header.intStringLength - (Int16)ByteReader.ReadInt16(stream);

            //Retrieve Segment Flags
            header.intSegmentFlags = (Int16)ByteReader.ReadInt16(stream);

            return header;
        } //end readHeader

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="dictProperties"></param>
        private static void ReadSegments(CrcCalculatorStream stream, InibinHeader header, Dictionary<UInt32, Object> dictProperties)
        {
            //Segment_01: 1x - 32bit Integers
            ReadSegment(stream, 0x0001, header, dictProperties);

            //Segment_02: 1x - 32bit Floats
            ReadSegment(stream, 0x0002, header, dictProperties);

            //Segment_03: 1x - 8bit Integers (Divided by 10)
            ReadSegment(stream, 0x0004, header, dictProperties);

            //Segment_04: 1x - 16bit Integers (Short)
            ReadSegment(stream, 0x0008, header, dictProperties);

            //Segment_05: 1x - 8bit Integers
            ReadSegment(stream, 0x0010, header, dictProperties);

            //Segment_06: 1x - 1bit Booleans
            if ((header.intSegmentFlags & 0x0020) != 0)
            {
                UInt32[] intKeys = ReadKeys(stream);

                //Extract one byte for every 8 keys.
                for (int i = 0; i < intKeys.Length; i+=8)
                {
                    int intByte = stream.ReadByte();
                    Boolean boolValue;

                    //Loop through each bit in the byte, converting each bit to a boolean value
                    for (int j = 0; j < 8; j++)
                    {
                        boolValue = Convert.ToBoolean(intByte & 0x01);
                        intByte = intByte >> 1;

                        if (i + j >= intKeys.Length) break;

                        Segment06Item seg06Obj = new Segment06Item();
                        seg06Obj.boolValue = boolValue;

                        dictProperties.Add(intKeys[i + j], seg06Obj);
                    } //end for 
                } //end for
            } //end if

            //Segment_07: 3x - 8bit Integers (RGB)
            ReadSegment(stream, 0x0040, header, dictProperties);

            //Segment_08: 3x - 32bit Floats (RGB - FragmentShaders)
            ReadSegment(stream, 0x0080, header, dictProperties);

            //Segment_09: 2x - 8bit Integers 
            ReadSegment(stream, 0x0100, header, dictProperties);

            //Segment_10: 2x - 32bit Floats - Radial Coordinates?
            ReadSegment(stream, 0x0200, header, dictProperties);

            //Segment_11: 4x - 8bit Integers (RGBA)
            ReadSegment(stream, 0x0400, header, dictProperties);

            //Segment_12: 4x - 32bit Floats (RGBA - FragmentShaders)
            ReadSegment(stream, 0x0800, header, dictProperties);

            //Segment_13: 1x - 16bit Integers (Offsets for Data Strings) (for extraction from data section)
            if ((header.intSegmentFlags & 0x1000) != 0)
            {
                UInt32[] intKeys = ReadKeys(stream);
                UInt16[] intOffsets = new UInt16[intKeys.Length];

                //Read all Offset Values for each key.
                for (int i = 0; i < intOffsets.Length; i++ )
                {
                    intOffsets[i] = (UInt16)ByteReader.ReadUInt16(stream);
                } //end for

                //ZipEntry does not have a Seek Method due to Zip Compression.
                // Instead of seeking,  extract and cache the data segment in memory.
                
                //Create data storage
                byte[] bytes = new byte[header.intStringLength - header.intStringOffset];
                
                //Check to see what position where we are... then move to the offset position
                int intPos = (int)(header.intStringOffset - stream.Position);
                if (stream.Position < header.intStringOffset)
                {
                    stream.Read(bytes, intPos, 0);
                } //end if
                
                //Read all of the data section into the memory buffer
                stream.Read(bytes, 0, (int)(header.intStringLength - header.intStringOffset));

                //Add the data for each key
                for (int i = 0; i < intOffsets.Length; i++)
                {
                    try
                    {
                        //Read the characters from each offset until a NULL character is found.
                        String str = "";
                        char chr = '0';

                        chr = (char)bytes[intOffsets[i]];
                        //(while not a null character... read another character and add it to the string)
                        for (int j = 1; chr != 0; j++)
                        {
                            str = str + chr;
                            chr = (char)bytes[intOffsets[i] + j];
                        } //end while

                        dictProperties.Add(intKeys[i], str);
                    }
                    catch (Exception)
                    {}
                } //end for
                
            } //end if

        } //end readSegments

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="intSegment"></param>
        /// <param name="dictProperties"></param>
        private static void ReadSegment(CrcCalculatorStream stream, int intSegment, InibinHeader header, Dictionary<UInt32, Object> dictProperties)
        {
            if ((header.intSegmentFlags & intSegment) != 0)
            {
                ReadValues(stream, ReadKeys(stream), intSegment, dictProperties);
            } //end if
        } //end readSegment

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private static UInt32[] ReadKeys(CrcCalculatorStream stream)
        {
            //Retrieve number of Keys
            UInt16 intKeyCount = (UInt16)ByteReader.ReadUInt16(stream);

            //Initialize the key array
	        UInt32[] intKeys = new UInt32[intKeyCount];
	        
            //Read all of the key values from the stream
            for (int i = 0; i < intKeyCount; i++)
            {
                intKeys[i] = (UInt32)ByteReader.ReadUInt32(stream);
	        } //end for

	        return intKeys;
    
        } //end readKeys

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="intKeys"></param>
        /// <param name="intFormat"></param>
        /// <param name="dictProperties"></param>
        private static void ReadValues(CrcCalculatorStream stream, UInt32[] intKeys, int intFormat, Dictionary<UInt32, Object> dictProperties)
        {
            ByteReader.ReadDelegate readDelegate = ByteReader.ReadUInt8;
            
            if (intFormat == 0x0001) readDelegate = ByteReader.ReadSegment01; // Segment_01: 1x - 32bit Integers
            if (intFormat == 0x0002) readDelegate = ByteReader.ReadSegment02; // Segment_02: 1x - 32bit Floats
            if (intFormat == 0x0004) readDelegate = ByteReader.ReadSegment03; // Segment_03: 1x - 8bit Integers (Divided by 10)
            if (intFormat == 0x0008) readDelegate = ByteReader.ReadSegment04; // Segment_04: 1x - 16bit Integers (Short)
            if (intFormat == 0x0010) readDelegate = ByteReader.ReadSegment05; // Segment_05: 1x - 8bit Integers
          //if (intFormat == 0x0020) readDelegate = ByteReader.ReadInt8;      // Segment_06: 1x - 1bit Booleans
            if (intFormat == 0x0040) readDelegate = ByteReader.ReadSegment07; // Segment_07: 3x - 8bit Integers (RGB)
            if (intFormat == 0x0080) readDelegate = ByteReader.ReadSegment08; // Segment_08: 3x - 32bit Floats (RGB - FragmentShaders)
            if (intFormat == 0x0100) readDelegate = ByteReader.ReadSegment09; // Segment_09: 2x - 8bit Integers 
            if (intFormat == 0x0200) readDelegate = ByteReader.ReadSegment10; // Segment_10: 2x - 32bit Floats - Radial Coordinates?
            if (intFormat == 0x0400) readDelegate = ByteReader.ReadSegment11; // Segment_11: 4x - 8bit Integers (RGBA)
            if (intFormat == 0x0800) readDelegate = ByteReader.ReadSegment12; // Segment_12: 4x - 32bit Floats (RGBA - FragmentShaders)

            foreach (UInt32 key in intKeys)
            {
                dictProperties.Add(key, readDelegate(stream));
            } //end foreach

        } //end ReadValues    

    } //end clsInibinReader

} //end LoLScrape
